Skip to content

fix(evm): align debug_trace fee with virtual ante path#967

Open
thomas-nguy wants to merge 7 commits into
crypto-org-chain:developfrom
thomas-nguy:thomas/debug-trace-balance-issue
Open

fix(evm): align debug_trace fee with virtual ante path#967
thomas-nguy wants to merge 7 commits into
crypto-org-chain:developfrom
thomas-nguy:thomas/debug-trace-balance-issue

Conversation

@thomas-nguy
Copy link
Copy Markdown

@thomas-nguy thomas-nguy commented May 27, 2026

Closes: #XXX

Description

The issue is that debug_traceTransaction was computing the upfront gas fee as gasLimit × gasPrice (the raw field),
which for EIP-1559 txs can be much higher than what was actually charged on-chain.

The fix computes the correct effective gas price (min(baseFee + gasTipCap, gasFeeCap)) — matching what geth's state
transition actually charges — and performs the deduction at the EVM stateDB level rather than going through the bank
module's spendable balance check. This means the trace will always succeed as long as the original tx succeeded
on-chain.


For contributor use:

  • Targeted PR against correct branch (see CONTRIBUTING.md)
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the module structure standards.
  • Wrote unit and integration tests
  • Updated relevant documentation (docs/) or specification (x/<module>/spec/)
  • Added relevant godoc comments.
  • Added a relevant changelog entry to the Unreleased section in CHANGELOG.md
  • Re-reviewed Files changed in the Github PR explorer

For admin use:

  • Added appropriate labels to PR (ex. WIP, R4R, docs, etc)
  • Reviewers assigned
  • Squashed all commits, uses message "Merge pull request #XYZ: [title]" (coding standards)

@thomas-nguy thomas-nguy requested a review from a team as a code owner May 27, 2026 08:38
@thomas-nguy thomas-nguy requested review from JayT106 and randy-cro and removed request for a team May 27, 2026 08:38
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the evm label May 27, 2026
@thomas-nguy
Copy link
Copy Markdown
Author

@claude review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 27, 2026

Claude finished @thomas-nguy's task in 5m 44s —— View job


PR Review: fix(evm): align debug_trace fee with virtual ante path

  • Gather context and understand the request
  • Read changed files
  • Analyze code changes
  • Post review feedback

Summary

The overall direction is correct — replacing the raw StateDB.SubBalance/AddBalance with DeductTxCostsFromUserBalance + RefundGas aligns the debug trace path with the virtual fee collector approach used in CheckEthGasConsume / ApplyTransaction. The BaseFee.BigInt() fix in TraceTx is a clean correctness improvement.

One significant bug was found, plus a few smaller concerns:


Issues Found

🔴 Deduct/Refund Price Asymmetry (EIP-1559)

debugTraceFeeAmount (in utils.go) correctly computes the effective gas price as min(tip+baseFee, feeCap), so the deduction uses the right amount. However, RefundGas (in gas.go:52) computes the refund as leftoverGas * msg.GasPrice — and for EIP-1559 transactions, msg.GasPrice is set to GasFeeCap by core.TransactionToMessage.

For a tx with baseFee=1gwei, tip=0, feeCap=5000gwei, gasLimit=2M, leftoverGas=1M:

  • Deducted: 1 * 2M = 2M gwei
  • Refund attempt: 5000 * 1M = 5B gwei ❌ — fee collector won't have enough → error/panic

The test masks this because gasTipCap=0 makes effectiveGasPrice == baseFee, so both sides use the same value. See inline comments for details.

🟡 Test Setup Ordering (state_transition_test.go:670)

SetupTestWithCb is called then immediately overridden by SetupTest. Whether the callback actually takes effect depends on internal test suite wiring — worth a double-check.

🟡 Nonce Semantics for Contract Creation in DebugTrace (state_transition.go:367)

The nonce is incremented eagerly on line 367 for all DebugTrace calls (including contractCreation). The Create branch later resets the nonce to msg.Nonce (line 444), which would undo the increment. This was the same in the old code, but worth documenting that the nonce reset in Create handles this.

🟢 Missing CHANGELOG Entry

The bot already flagged this. An entry under ## Unreleased like:

* (rpc) [#967](https://github.com/crypto-org-chain/ethermint/pull/967) fix(evm): align debug_traceTransaction fee deduction with virtual ante path (EIP-1559 effective gas price, BigInt BaseFee parse).

🟢 No Breaking State Changes

This PR only affects the debug_trace* RPC path (no ante handler, no consensus, no state serialization changes). It is not a state-breaking change and is safe to merge to a release branch from a consensus perspective.


Verdict

The core issue being fixed is real and the approach is right. The EIP-1559 refund asymmetry should be resolved before merge — it would cause RefundGas to fail or over-refund whenever gasFeeCap > effectiveGasPrice (which is the common case for EIP-1559 txs). All inline comments have been posted above.

Comment thread x/evm/keeper/utils.go
Comment thread x/evm/keeper/state_transition_test.go
Comment thread x/evm/keeper/state_transition_test.go
Comment thread x/evm/keeper/state_transition.go Outdated
Comment thread x/evm/keeper/grpc_query.go
@thomas-nguy thomas-nguy force-pushed the thomas/debug-trace-balance-issue branch from 8cb7040 to 646ab0c Compare May 28, 2026 09:07
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 87.09677% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.88%. Comparing base (30214c2) to head (96731ac).

Files with missing lines Patch % Lines
x/evm/keeper/gas.go 60.00% 1 Missing and 1 partial ⚠️
x/evm/keeper/grpc_query.go 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #967      +/-   ##
===========================================
+ Coverage    44.84%   44.88%   +0.03%     
===========================================
  Files          197      197              
  Lines        16952    16967      +15     
===========================================
+ Hits          7602     7615      +13     
- Misses        8538     8539       +1     
- Partials       812      813       +1     
Files with missing lines Coverage Δ
x/evm/keeper/simulate.go 85.98% <100.00%> (ø)
x/evm/keeper/state_transition.go 74.53% <100.00%> (+0.09%) ⬆️
x/evm/keeper/utils.go 84.00% <100.00%> (+1.77%) ⬆️
x/evm/keeper/gas.go 85.71% <60.00%> (-4.61%) ⬇️
x/evm/keeper/grpc_query.go 72.19% <33.33%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thomas-nguy
Copy link
Copy Markdown
Author

@claude review this PR

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 29, 2026

Claude finished @thomas-nguy's task in 4m 20s —— View job


PR Review: fix(evm): align debug_trace fee with virtual ante path

  • Gather context and understand the request
  • Read changed files
  • Analyze code changes
  • Post review feedback

Summary

The fix is correct and the previous review's top concern (deduct/refund price asymmetry) has been addressed. Here's the full picture:


Key Changes and Assessment

x/evm/keeper/utils.go — New helper functions ✅

debugTraceGasPrice correctly computes min(tip + baseFee, feeCap) for EIP-1559 and falls back to msg.GasPrice for legacy txs. debugTraceFeeAmount multiplies by gasLimit. Both are well-named and isolated.

x/evm/keeper/state_transition.go — Core fix ✅

Deduction (line 378): uses debugTraceFeeAmount(msg, cfg.BaseFee) — effective price × gasLimit. ✅
Refund (lines 538–544): uses debugTraceGasPrice(msg, cfg.BaseFee) × leftoverGas — same effective price. ✅

The previous review's concern about RefundGas asymmetry no longer applies — the debug trace path bypasses RefundGas entirely and handles the refund inline with the correct price. Net deduction = effectiveGasPrice × gasUsed, matching on-chain behavior exactly.

The removal of the debugFn closure (previously deferred, now inline) also eliminates a subtle ordering hazard: the old code could call the deferred debugFn after the OnTxEnd tracer callback, which had the wrong gas accounting.

x/evm/keeper/gas.goRefundGasWithPrice

Clean refactor: RefundGas now delegates to RefundGasWithPrice. The new function is not called from the debug trace path (which uses stateDB.AddBalance directly), but it's a valid separation for other callers. No issues.

x/evm/keeper/grpc_query.goBaseFee.BigInt() fix ✅

Replacing big.NewInt(req.BaseFee.Int64()) with req.BaseFee.BigInt() avoids silent truncation for baseFees > math.MaxInt64. The added !req.BaseFee.IsNil() guard is correct. Minor: sdkmath.Int.BigInt() returns a copy, so no defensive copying is needed by the caller — this is already the case.


Issues Found

🟡 Redundant SetupTestWithCb call in test (state_transition_test.go:664)

suite.SetupTestWithCb(t, func(...) { ... NoBaseFee = false ... })  // ← this init is discarded
suite.mintFeeCollector = true
suite.SetupTest()  // ← this reinitializes from scratch with NoBaseFee = true

The first SetupTestWithCb at line 664 fully initializes the app with NoBaseFee=false, but the SetupTest() call at line 672 immediately reinitializes it (with NoBaseFee=true). The fee market configuration from the first call is discarded entirely. The test still works because cfg.BaseFee = baseFee is set manually on line 697, bypassing the module state.

The intent appears to be testing with an active base fee, but the module is actually initialized with NoBaseFee=true. The test validates the right thing (effective price deduction), but the setup is misleading. Consider restructuring:

// Correct: set mintFeeCollector before SetupTest so the callback reads it
suite.mintFeeCollector = true
suite.SetupTestWithCb(t, func(a *evmd.EthermintApp, genesis evmd.GenesisState) evmd.GenesisState {
    feemarketGenesis := feemarkettypes.DefaultGenesisState()
    feemarketGenesis.Params.EnableHeight = 1
    feemarketGenesis.Params.NoBaseFee = false
    genesis[feemarkettypes.ModuleName] = a.AppCodec().MustMarshalJSON(feemarketGenesis)
    // ... fee collector minting ...
    return genesis
})

🟡 Test only covers gasTipCap=0 (same as previous review)

With gasTipCap=0, effectiveGasPrice == baseFee, so deduction and refund both operate on the same value even without the fix. A complementary case with gasTipCap > 0 and gasFeeCap >> baseFee + tip would more directly validate the asymmetry was fixed. Since the refund path (debugTraceGasPrice) uses the same min(tip+baseFee, feeCap) formula as the deduction, the code is correct for all cases — but the test coverage gap is worth noting.

🟢 No state-breaking changes

This PR only modifies the debug_trace* RPC path. No ante handler, no consensus logic, no state serialization changes. Safe to merge to a release branch.


Verdict

The core fix is correct. The EIP-1559 refund asymmetry from the previous review has been resolved. The two remaining points above are minor code quality issues — the test redundancy is cosmetic and doesn't affect correctness. The PR is ready to merge once the test setup is cleaned up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant